<?

/** dzkone readset functionality.
 *
 *  This collection of functions (libary) is licensed under GPL2.
 *  See LICENSE or www.gnu.org for more details.
 *
 *  @author: JQ + Henri Ranki 11.6.2001
 *
 *  $Id: dzkone_write.php,v 1.4 2005/03/12 17:06:54 jyry Exp $
 */

/** Writes out (insert/update) a set.
 *  @param dzKone $Kone dzKone object to operate with.
 *  @return int Returns status on write: -1 == error (when locking, reading or
 *      executing an sql clause), 0 == no write, set is up to
 *      date, 1 == write was successful
 */
function __dzkone__writeSet(&$Kone) {
    include("sodebug.inc");
#    $result = __dzkone_validateSet($Kone, TISKI);
#    if (!$result) {
#        soDebug("__dzkone_writeset: validation failed", SOD_LO);
#        return -1; // <-- EXIT
#    };

    /* In case the db driver does not support transactions natively,
     * all sql update/inser/delete commands will get their undo pills
     * in this array. So that when an modification fails, all the
     * undo pills are run in order to get the original state of tables
     * affected. */
    $undos = array();

    /* if cached taffel is found, load it or read the set from db */
    $cache_filename = $Kone->__getCacheFileName($Kone->__getMasterKeyVals());
    $file = soFileToString(soExtractVal($cache_filename, "file://", 2));

    if ($file) {
        $Kone->__Purkki->setStore(TAFFEL, unserialize($file));
    } else {
        /* No cached TAFFEL available?
           Check/set for default key values before trying reading */
        $defaults = __dzkone_writeset_setDefaults($Kone);

        include_once("dzkone_read.php");
        if (!__dzkone__readSet($Kone, 1)) return -1; // <-- EXIT
    }; // else if
    __dzKone_writeset_runAutoNum($Kone);
    soDebug("__dzkone_writeset: __Purkki contains ".
            $Kone->__Purkki->dump(), SOD_DET);

    /* Start transaction */
    $Kone->__Soda->runTransact("begin");

    /* flag to indicate if anything gets written or not */
    $result = 0;

    /* loop tables */
    $mtablename = $Kone->__Struct->getMaster("tablename");
    $tables = $Kone->__getColsByTable(1);
    $tablenames = $tables->getkeys();
    foreach ($tablenames as $tablename) {

        if ($tablename != $mtablename) {
            /* See the comment on the below function declaration for
               details on this tricky bit. It is essential task to do. */
            __kone_writeset_fixKeyVals($Kone, $tablename);
        };

        $Kone->__Soda->clearAllSections();
        $soda = $tables->getItem($tablename);
        $Kone->__Soda->setTable($tablename, 1);

        /* sort out row counts in taffel and tiski */
        $taffel_count = $Kone->__getRowCount(TAFFEL, $tablename, "max");
        $tiski_count = $Kone->__getRowCount(TISKI, $tablename, "max", 0);

        /* Where colnamess with order colnames */
        $key = $soda["wherecols"];
        soInsElement($key, $soda["ordercols"], -1);

        /* Walk through TISKI and look for rows to be inserted or
         * updated */
        $updates = array();   /* tiski rownos. to update */
        $deletes = array();   /* tiski rownos. to delete */
        $update_vals = array();
        $inserts = array();   /* tiski rownos. to insert */
        $insert_vals = array();
        $found_taf = array(); /* Rownos. of taffel rows found */
        $cols = $soda["ordercols"];
        if (!count($soda["ordercols"])) {
            $cols = $soda["wherecols"];
        };
        for ($i = 0; $i < $tiski_count; $i++) {
                $row_no = __dzkone__key_exists($Kone,
                                               $i,
                                               $cols,
                                               $tablename);
                if ($row_no != -1) {
                    $updates[] = $i;
                    $update_vals[] =
                        $Kone->__Purkki->getStoreItem(TISKI,
                            $cols[0], $i);
                    $found_taf[] = $i;
                } else {
                    $inserts[] = $i;
                    $insert_vals[] =
                        $Kone->__Purkki->getStoreItem(TISKI,
                            $cols[0], $i);
                }; // else if
#            } else {
#                $updates[] = $i;
#                $found_taf[] = $i;
#            }; // else if
        }; // for

        /* Get the taffel rownos. that where not found, in other words,
         * those taffel rows that should be deleted. */
        if (count($cols)) {
            $ovals =
                $Kone->__Purkki->getStoreCol(TAFFEL, $cols[0]);
            for ($i = count($ovals) -1; $i >= 0; $i--) {
                if (!in_array($ovals[$i], $insert_vals) &&
                    !in_array($ovals[$i], $update_vals)) {
                    $deletes[] = $i;
                    $u_pos = soArrayFind($update_vals, $ovals[$i]);
                    soDelElement($updates, $u_pos);
                }; // if
            }; // for
        }; // if
#echo "upit ".implode(" ", $cols)."/".implode(" ", $update_vals)."<br>";
#echo "insit ".implode(" ", $cols)."/".implode(" ", $insert_vals)."<br>";

#echo "table=$tablename tiskicount=$tiski_count<br>";
#echo implode(" ", $inserts)." i<br>";
#echo implode(" ", $updates)." u<br>";
#echo implode(" ", $found_taf)." taf<br>";
#echo implode(" ", $deletes)." d<br>";
        $actions = array(SODA_UPDATE, SODA_INSERT, SODA_DELETE);
        $row_nos = array($updates, $inserts, $deletes);
        for ($a = count($actions) -1; $a >= 0; $a--) {
            $icount = count($row_nos[$a]);
            for ($i = 0; $i < $icount; $i++) {
                $result = __dzkone__writeSet_sub($Kone,
                                                 $actions[$a],
                                                 $row_nos[$a][$i],
                                                 $tablename);
                if ($result != 1) break; // <-- BREAK
            }; // for $i
        }; // for $a
        #$Kone->__Purkki->setStoreItem(TISKI, $column, 0, $value);

    }; // foreach table

    /* Finish transaction */
    if ($result < 0) {
        $Kone->__Soda->runTransact("rollback");
        $Kone->__SeqKey->finishUpdate("cancel");

        /* v- non-transaction capable driver rollback */
        if ($Kone->__Soda->getTransactStatus == -1) {
            /* Run all undo pills in reverse order when driver does not
            * support transaction natively. */
            while (count($undos)) {
                $undo = array_pop($undos);
                $undo->run();
            }; // while
        }; // if transactstatus == -1
    } else {
#        $Kone->__Soda->runTransact("rollback");
        $Kone->__Soda->runTransact("commit");
        $Kone->__SeqKey->finishUpdate("commit");
    };

    include_once("dzkone_lock.php");
    if (!__dzkone__lockSet($Kone, SOLM_UNLOCK, $cache_filename)) $result = -1;

    if ($result >= 0) {
        $Kone->__OutItu->setBlockItem(DZI_CONTROL, DZK_RESULT, $result);
        $Kone->__OutItu->setBlockItem(DZI_CONTROL, DZK_KEYS,
                                      $Kone->__getMasterKeyVals());
    } else {
        $Kone->__OutItu->setBlockItem(DZI_CONTROL, DZK_RESULT,
                                      DZR_WRITE_ERROR);
        $Kone->__OutItu->setBlockItem(DZI_DATA, DZK_SQL,
                                      $Kone->__Soda->Clause);
    };

    return $result;
} // __dzkone__writeSet


/** Actual routine to delete, update or insert rows.
 *  @param dzKone $Kone dzKone object to operate with.
 *  @param $Action SODA_action (update, delete or insert).
 *  @param $RowNo Row no. in taffel to delete, or in tiski to update or insert.
 */
function __dzkone__writeSet_sub(&$Kone, $Action, $RowNo, $TableName) {
    $Kone->__Soda->setAction($Action);
    ($Action == SODA_DELETE) ? ($store = TAFFEL) : ($store = TISKI);

    /* Get taffel or tiski row */
    $pdcols = $Kone->__Struct->getColNamesByType(DZSCT_DATA, $TableName, 1);
    $dcols = $pdcols;
    $ocols = $Kone->__Struct->getKeyColNames($TableName, 2);
    foreach ($ocols as $ocol) {
        array_push($dcols, $TableName.".".$ocol);
    }; // for

    $store_row = $Kone->__Purkki->getRow($store, $RowNo, $dcols);
    if ($Action == SODA_UPDATE) {
        $taf_arr = $Kone->__Purkki->getRow(TAFFEL, $RowNo, $dcols, 1);
        $cols = soCompArrays($store_row->getAsArray(), $taf_arr);

        /* If the TISKI & TAFFEL row are identical, then there is no point
         * continuing this routine and update the row. */
        if (!count($cols)) return 1; // <-- EXIT

        /* Check whether any update column is order column */
        $i = count($cols);
        while ((--$i >= 0) && (!in_array($TableName.".".$ocols[$i], $cols)));

        /* Order col updates ahead. No good. Instead, update all datacols */
        if ($i != -1) $cols = $pdcols;

        $store_row = $Kone->__Purkki->getRow($store, $RowNo, $cols);
    }; // if
    
    /* Sort out data column section for soDa */
    if (($Action != SODA_DELETE) &&
        (count($store_row->getAsArray()))) {
        $keys = $store_row->getKeys();
        $cols = array();
        foreach ($keys as $key) {
            $cols[] = $key.":".$Kone->__Struct->getDatatype($key);
        }; // foreach
        $res = $Kone->__Soda->setSection(SODA_DATA,
            $cols, soForceNumKeyArray($store_row->getAsArray()),
            SODA_RESET);
        foreach ($keys as $key) {
            $dtype = $Kone->__Struct->getColProperty($key, DZCP_TYPE);
            if (!$dtype) $dtype = SODT_TEXT;
            $Kone->__Soda->setDataType($key, $dtype);
        }; // foreach
    } // if != delete

    /* sort out where section for soDa */
    if ($Action == SODA_INSERT) {
        $section = SODA_DATA;
        $method = SODA_REPLACE;
        $wstore = TISKI;
    } else {
        $section = SODA_WHERE;
        $method = SODA_RESET;
        $wstore = TISKI;
    }; // else if insert

    /* Add where columns */
    $wcols = $Kone->__Struct->getColNamesByType(DZSCT_KEY, $TableName, 1);
    $taf_wvals = $Kone->__Purkki->getRow($wstore, $RowNo, $wcols);
    $wvals = $taf_wvals->getAsArray(1);

    for ($i = count($wcols) -1; $i >= 0; $i--) {
        $wcols[$i] = $wcols[$i].":".$Kone->__Struct->getDatatype($wcols[$i]);
    }; // for

    $ocols = array();
    $ovals = array();
    $mkeycount = $Kone->__Struct->getMaster("keycount");
    for ($i = count($wcols) -1; $i >= $mkeycount; $i--) {
        $ocols[] = array_pop($wcols);
        $ovals[] = array_pop($wvals);
    }; // for

    if ($Action == SODA_DELETE) {
        $taf_vals = $store_row->getItems($ocols);
        for ($i = count($ocols) -1; $i >= 0; $i--) {
            if (!soIsNull($taf_vals[$i])) $ovals[$i] = $taf_vals[$i];
        }; // for
    } else {
    };
    $sodares = $Kone->__Soda->setSection($section, $wcols, $wvals, $method);
    if ($sodares == -1) {
        soDebug("__dzKone__writeset_sub: where column/value ".
                "count mismatch", SOD_HI);
    }; // if

    if ($Action == SODA_DELETE) {
        $store_id = TAFFEL;
    } else {
        $store_id = TISKI;
    }; // else if delete

    /* Add order columns, if any */
    if (count($ocols)) {
        $sodares = $Kone->__Soda->addToSection($section,
                                               $ocols[0],
                                               $ovals[0],
                                               SODA_REPLACE);
    }; // if

    if (($sodares != -1) &&
        (($Kone->__Soda->getAction() == SODA_DELETE) ||
        (count($store_row->getAsArray())))) {
        $sql_result = $Kone->__Soda->run();
        if (!$sql_result) {
            $result = DZR_WRITE_ERROR;
        } else {
            /* flag to indicate that at on row was written */
            $result = 1;
        }; // if !$sql_result

    }; // if delete

    /* Sort out undo pills */
    $undo = $Kone->__Soda;
    if ($Action == SODA_DELETE) {
        $undo->setAction(SODA_INSERT);
        $cols = soCompArrays($store_row->getAsArray(),
            $Kone->__Purkki->getRow(TISKI, $RowNo, $dcols, 1));
        if (count($cols)) {
            $row = $Kone->__Purkki->getRow(TISKI, $i, $cols);
            $undo->setSection(SODA_WHERE, $cols, $row);
            $undos[] = $undo;
        };
    } else {
        $undo->setAction(SODA_DELETE);
        $undo->setSection(SODA_WHERE, $cols, $vals);
        $undos[] = $undo;
    }; // else if delete

    return $result;
}; // __dzkone__writeSet_sub


/** Empties key column values for specified table in __Purkki.
 *  This is important routine to do, because in some cases there may be too
 *  many values in key columns (when deleting a row of a group.
 *  When the readSet is done, it includes key column values for each row
 *  read and this causes confusion when trying to count the maximum number of
 *  rows for the table. N.B. This does not delete the last key column, the
 *  order column, as it is handled by the UI or by auto-numbering.
 *  The key column values (excluding the order column) for the tables are
 *  fetched from the master table.
 */
function __kone_writeset_fixKeyVals(&$Kone, $TableName) {
    $kcols = $Kone->__Struct->getKeyColNames($TableName);
#    $kcols = $Kone->__Struct->getColNamesByType(DZSCT_KEY, $TableName, 1);
    $mkeycols = $Kone->__Struct->getMasterKeyColNames(1);
    $mkeycount = count($mkeycols);
    $mkeyvals = $Kone->__getMasterKeyVals();

    /* Drop off the order col(s) */
    for ($i = count($kcols) -1; $i >= $mkeycount; $i--) array_pop($kcols);

    $rcount = $Kone->__getRowCount(TISKI, $TableName, "max", 0);
    $kvals = array();
    if ($rcount) {
        for ($i = count($mkeycount) -1; $i >= 0; $i--) {
            $col = $Kone->__Struct->getCol($kcols[$i]);
            if (!$col) {
                $vals = array();
                array_pad($vals, $rcount, $mkeyvals[$mkeycols[$i]]);
                $Kone->__Purkki->setStoreCol(TISKI, $kcols[$i], $vals);
            }; // if
        }; // foreach
    }; // if
}; // __kone_writeset_fixKeyVals


/** Checks whether a row in TISKI exists in TAFFEL.
 *  Called by __dzkone__writeSet. This assumes, the rowno. passed does indeed
 *  return a row from TISKI.
 *  @param object dzKone $Kone dzKone that contains dzPurkki object.
 *  @param int $RowNo Rowno. to check key of.
 *  @param array/string $OrderCols Order column names.
 *  @return int Returns the found row no in TAFFEL or -1 when not found.
 */
function __dzkone__key_exists($Kone, $RowNo, $OrderCols, $TableName) {
    $row = implode("\t",
                   $Kone->__Purkki->getRow(TISKI, $RowNo, $OrderCols, 1));
    $result = $Kone->__getRowCount(TAFFEL, $TableName, "max", 0);
    do {
        $result--;
        $taf_row = implode("\t", $Kone->__Purkki->getRow(TAFFEL, 
                                                         $result,
                                                         $OrderCols,
                                                         1));
    } while (($result != -1) && ($row != $taf_row));
    return $result;
}; // __dzkone__key_exists


/** Sets values for "auto" columns.
 *  @param dzKone $Kone dzKone object to operate with.
 *  Looks for columns having "auto" set
 */
function __dzKone_writeset_runAutoNum(&$Kone) {
    $mtable = $Kone->__Struct->getMaster("tablename");
    $keys = $Kone->__Struct->getColNames();
    foreach ($keys as $key) {
        $tablename = soExtractVal($key, ".", 1);

        $key_no = $Kone->__Struct->isKeyCol($key);
        if ($key_no >= 0) {
            $col = $Kone->__Struct->getKeyCol($key);
            if ($mtable != $tablename) {
                $default = $col->Default;
            };
        } else {
            $col = $Kone->__Struct->getCol($key);
            $default = $col->Options->getItem(DZCP_DEFAULT);
        }; // else if

        if ($default == DZDCV_ROWNO) {
            $Kone->__Struct->setColProperty($key, DZCP_TYPE, SODT_INT);

            /* Handle TISKI: empty previous values and re-set */
            $Kone->__Purkki->setStoreCol(TISKI, $key, array());
            $vals = array();
            $icount = $Kone->__getRowCount(TISKI, $tablename);
            for ($i = 0; $i < $icount; $i++) $vals[$i] = ($i +1);
            $Kone->__Purkki->setStoreCol(TISKI, $key, $vals);
        }; // if
    }; // foreach
} // __dzKone_writeset_runAutoNum


/** Check & set default key values
 *  Sets master key value defaults, along with sequential keys, if required
 *  Used by __writeSet.
 *  @param object dzKone $Kone dzKone that contains dzPurkki object.
 *  @return int Returns new key number if seq. key was set, otherwise NULL
 */
function __dzkone_writeset_setDefaults(&$Kone) {

    $error_msg = "__dzkone_writeset_setDefaults: ";
    $error = 0;
    /* Fist, some validity checks */
    if (!($mtable = $Kone->__Struct->getMaster("tablename"))) {
        $error = 1;
    }; // if $mtable...
    if (($keys = $Kone->__getMasterKeyVals()) == NULL) $error = 2;
    if ($error) {
        soDebug($error_msg."ERROR ".$error, SOD_HI);
        return NULL; // <-- EXIT
    };

    /* More checks: if the 1st key of the main table == "",
     * *and* it's columns property "Value" is set to "auto", then it's
     * obvious that next sequential key should be read and set. */
    $mcols = $Kone->__Struct->getColNames($mtable, 1);

    /* Loop all master cols */
    $result = NULL;
    foreach ($mcols as $mcol) {

        $default = $Kone->__Struct->getColProperty($mcol, DZCP_DEFAULT);
        if (!$default) continue; // <-- CONTINUE

        $len = strlen(DZDCV_AUTO);
        if (substr($default, 0 , $len) == DZDCV_AUTO) {
            $def_cols = soExplode($default, ";");
            $default = array_shift($def_cols);
            $serie = array();
            foreach ($def_cols as $def_col) {
                $serie[soExtractVal($def_col, ".", 2)] =
                    $Kone->__Purkki->getStoreItem(TISKI, $def_col, 0);
            }; // foreach
        }; // if

        switch ($default) {
            case DZDCV_TIME:
                $result = time() /SECS_PER_DAY;
                break;

            case DZDCV_AUTO:
                $owner = $Kone->__InItu->getBlockItemAt(DZI_CONTROL,
                                                        DZK_USER);
                $session_id = $Kone->__InItu->getBlockItemAt(DZI_CONTROL,
                                                            DZK_SESSION);
                $Kone->__SeqKey->setOwner($owner, $session_id);
                $serie[soExtractVal($mcol, ".", 2)] = "";
                $Kone->__SeqKey->setTarget($mtable, $serie);
                $result = $Kone->__SeqKey->startUpdate();
                if ($result) {
                    $result = $Kone->__SeqKey->getKey();
                } else {
                    $result = NULL;
                };
                break;
        }; // switch

        if (!soIsNull($result)) {
            $i = max(1, count($Kone->__Purkki->getStoreCol(TISKI, $mcol)));
            $values = $Kone->__Purkki->getStoreCol(TISKI, $mcol);
            while (--$i >= 0) {
                if ($values[$i]) {
                    soDebug($error_msg."$mcol row $i already set to '".
                            $values[$i]."'", SOD_HI);
                } else {
                    $Kone->__Purkki->setStoreItem(TISKI, $mcol, $i, $result);
                }; // else if
            }; // while
        }; // if

    }; // foreach
    if (!$result) {
        soDebug($error_msg."nothing set for table '". $mtable."'", SOD_LO);
    }; // for

    soDebug("__dzkone_writeset_setDefaults: result=$result ", SOD_DET);
    return $result;
} // __dzkone_writeset_setDefaults


/** Validates a set/store in Purkki.
 *  When a set being validated contains invalid values, information
 *  about the issue is stored in __OutItu.
 *  @param dzKone $Kone dzKone object to operate with.
 *  @param storeid $StoreID Store to be validated.
 *  @return bool True(1) or false(0) whether set contains validate values
 *  only.
 */
function __dzkone_validateSet(&$Kone, $StoreID) {
    if (!$Kone->__Purkki->isStore($StoreID)) {
        return 0; // <-- EXIT
    }; // if

    $store = $Kone->__Purkki->getStore($StoreID);
    $keys = $store->getKeys();
    $result = 1;

    /* Unique check */
    foreach ($keys as $key) {
        if ($Kone->__Struct->getColProperty($key, DZCP_UNIQUE)) {
            $values = $store->getItem($key);
            if (count(soGetArrayDoubles($values))) {
                $Kone->__OutItu->setBlockItem(DZI_CONTROL, DZK_RESULT,
                                              DZR_INCOMPLETE_FORM);
                $Kone->__OutItu->setBlockItem(DZI_CONTROL, DZK_STATUS, $key);
                $result = 0;
                break; // <-- EXIT foreach
            }; // if soGetArrayDoubles
        }; // if DZCP_UNIQUE
    }; // foreach

    return $result;
} // __dzkone_validateSet


?>
